﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using StockMonitor.Configuration;
using CLFramework.Utility;

namespace StockMonitor.Business
{
    /// <summary>
    /// ETF MA指标策略计算器
    /// </summary>
    /// <remarks>
    /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
    /// 编写时间：<datetime>2012-10-17 3:49 PM</datetime>
    /// </remarks>
    public class ETFMACalculator : ICaptialCalculator
    {
        #region Config

        /// <summary>
        /// 涨幅止盈率.
        /// </summary>
        /// <value>
        /// 默认千分之三
        /// </value>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 3:58 PM</datetime>
        /// </remarks>
        double UpRate { get; set; }

        /// <summary>
        /// 跌幅止损率
        /// </summary>
        /// <value>
        /// 默认50%
        /// </value>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 3:58 PM</datetime>
        /// </remarks>
        double DownRate { get; set; }

        /// <summary>
        /// MA分钟数，如5MA
        /// </summary>
        /// <value>
        /// 默认5分钟
        /// </value>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 3:58 PM</datetime>
        /// </remarks>
        int MAMins { get; set; }

        /// <summary>
        /// MA计算的分钟（天）数，如21MA
        /// </summary>
        /// <value>
        /// The index of the MA.
        /// </value>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-25 4:37 PM</datetime>
        /// </remarks>
        int MAIndex { get; set; }

        #endregion

        #region Status

        /// <summary>
        /// 上次买入价格
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:14 PM</datetime>
        /// </remarks>
        public double lastTradePrice;

        /// <summary>
        /// 上次交易之后的最高价格
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:38 PM</datetime>
        /// </remarks>
        public double lastMaxPrice;

        /// <summary>
        /// 是否已经买入，等待离场
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:23 PM</datetime>
        /// </remarks>
        public bool waitSell = false;

        /// <summary>
        /// 上次买入之后涨幅是否超过3‰
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:41 PM</datetime>
        /// </remarks>
        public bool overRate = false;

        #endregion

        #region Calculate

        /// <summary>
        /// 是否已经初始化
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:11 PM</datetime>
        /// </remarks>
        bool isInited = false;

        /// <summary>
        /// 历史价格
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:11 PM</datetime>
        /// </remarks>
        List<double> historyPrice;

        /// <summary>
        /// 最后一次MA数
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:08 PM</datetime>
        /// </remarks>
        double lastMA;

        /// <summary>
        /// 上次行情价格
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:42 PM</datetime>
        /// </remarks>
        double lastPrice;

        /// <summary>
        /// 本次MA数
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:09 PM</datetime>
        /// </remarks>
        double currentMA;

        #endregion

        #region ICaptialCalculator Members

        /// <summary>
        /// 计算器标识
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-16 4:43 PM</datetime>
        /// </remarks>
        public string Key { get { return "ETFMA"; } }

        /// <summary>
        /// 股票代码
        /// </summary>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-16 4:43 PM</datetime>
        /// </remarks>
        public string StockCode { get; set; }

        /// <summary>
        /// 初始化指标计算器
        /// </summary>
        /// <param name="stockCode">股票代码</param>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-16 4:43 PM</datetime>
        /// <returns>是否初始化成功</returns>
        /// </remarks>
        public bool Init(string stockCode)
        {
            UpRate = AppSettings.GetValue<double>("ETFMAUpRate", 0.003, true);
            DownRate = AppSettings.GetValue<double>("ETFMADownRate", 0.5, true);
            MAMins = AppSettings.GetValue<int>("ETFMAMins", 5, true);
            MAIndex = AppSettings.GetValue<int>("ETFMAIndex", 21, true);

            if (string.IsNullOrEmpty(stockCode))
                throw new ArgumentNullException("stockCode", "指标初始化错误，必须指定股票代码");

            this.StockCode = stockCode;

            //历史价格要改  2012-11-5测试：historyPrice成功拿到了前N个5分钟片的开盘价
            historyPrice = BusinessFactory.GetETFPriceProvider().GetHistoryPrice(stockCode, MAIndex + 1, MAMins);

            if (historyPrice == null)
                return false;

            lastMA = GetSMA(MAIndex, historyPrice);//21MA，每隔5分钟的价格序列（每5分钟收盘价）

            isInited = true;


            if (Configuration.AppSettings.GetValue<bool>("IsDebug"))
            {
                Log.Info(string.Format("【{0}】 ETFMA侦测器初始化完毕", StockCode));
            }

            return true;

            //note: 【中途关掉程序，再打开的情况下，上次交易相关的信息需要保留】
            //note: 是否可通过将状态保存在sqlite中进行可持续化？？
            // [StockCode]
            // [LastTradePrice] 上次成交价
            // [LastMaxPrice] 上次成交后最高价
            // [WaitSell] 是否已买入
            // [OverRate] 是否已逾价3%，准备止盈
            // [Date]
            //note: 另外，这些属性设置成public的，可从外面直接设置※❤

            //实际上，这个系统不同于MA3521，是实时系统，整个交易中间如果断一下，就不准了，
            //要实时采集数据，除非将价格保存独立于进程外，并保证价格连续、不断、稳定
        }

        /// <summary>
        /// 检测指标信号
        /// </summary>
        /// <param name="currentPrice">当前价格（收盘价）</param>
        /// <returns>
        /// 应发信号
        /// </returns>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-16 4:43 PM</datetime>
        /// </remarks>
        public Enums.Signal DetectSignal(double currentPrice)
        {
            if (!isInited)
                this.Init(StockCode);

            var etfprice = BusinessFactory.GetETFPriceProvider();

            //每次轮循更新历史价格(因为历史价格是不连续的)
            historyPrice = etfprice.GetHistoryPrice(StockCode, MAIndex + 1, MAMins);

            double openPrice = etfprice.GetBeforePrice(StockCode, MAMins); //N分钟K线的开盘价

            currentMA = GetSMA(MAMins, historyPrice, currentPrice);
            var tempsg = Enums.Signal.None;

            //note: 由于离场就是卖空，那么一天之内不可能出现“连买”或“连卖”情况

            //价格从下上穿21MA{REF(CLOSE,1)≤REF(21MA,1)&CLOSE>21MA）}（21已定）&收阳线（CLOSE>OPEN），做多
            if ((!waitSell) && (currentPrice <= lastMA && currentPrice > currentMA) && currentPrice > openPrice)
            {
                tempsg = Enums.Signal.Buy;

                lastTradePrice = currentPrice;//买入并记录交易价格【此处不准，应该用实际成交的价格，后面再优化】
                lastMaxPrice = currentPrice;
                waitSell = true;

                if (Configuration.AppSettings.GetValue<bool>("IsDebug"))
                {
                    Log.Info(string.Format("【{0}】价格从下上穿21MA｛REF(CLOSE,1)≤REF(21MA,1)&CLOSE>21MA）｝（21已定）&收阳线（CLOSE>OPEN），做多", StockCode));
                }
            }
            else if (waitSell) //如果已经买入，等待卖出，则判断是否符合离场条件、准备离场条件
            {
                if (DateTime.Now.ToString("hh:mm") == "14:59")  //收盘之前14:59离场
                {
                    tempsg = Enums.Signal.Sell;
                }

                if (lastPrice >= lastMA && currentPrice < currentMA) //收盘价格下穿21MA（CLOSE<21MA），离场
                {
                    tempsg = Enums.Signal.Sell;

                    if (Configuration.AppSettings.GetValue<bool>("IsDebug"))
                    {
                        Log.Info(string.Format("【{0}】 收盘价格下穿21MA（CLOSE<21MA），离场", StockCode));
                    }
                }

                if ((!overRate) && (currentPrice > lastTradePrice * (1 + UpRate))) //保存价格上涨是否幅度大于3‰
                {
                    overRate = true;

                    if (Configuration.AppSettings.GetValue<bool>("IsDebug"))
                    {
                        Log.Info(string.Format("【{0}】 保存价格上涨幅度大于3‰", StockCode));
                    }
                }

                if (currentPrice > lastMaxPrice) //保存交易后的最大收盘价
                {
                    lastMaxPrice = currentPrice;
                }

                if (overRate && ((lastMaxPrice - currentPrice) / (lastMaxPrice - lastTradePrice) >= DownRate)) //价格上涨幅度大于3‰，最大的收盘价涨幅回落50%以上（含），离场。（3‰、50%，这两个数据需要测试，不是最优的）。
                {
                    tempsg = Enums.Signal.Sell;

                    if (Configuration.AppSettings.GetValue<bool>("IsDebug"))
                    {
                        Log.Info(string.Format("【{0}】 价格上涨幅度大于3‰，最大的收盘价涨幅回落50%以上（含），离场", StockCode));
                    }
                }

                if (tempsg == Enums.Signal.Sell)
                {
                    waitSell = false;  //重置为待买入状态【此处不准，应该在交易成功时才能确定】
                    overRate = false;
                    lastTradePrice = 0;
                    lastMaxPrice = 0;
                }

                //note:不能在这个类中确定交易是否完成，是个大麻烦，因为状态的重置需要知道确切的交易结果
            }

            //保存上次价格，上次MA
            lastMA = currentMA;
            lastPrice = currentPrice;

            if (Configuration.AppSettings.GetValue<bool>("IsDebug"))
            {
                Log.Info(string.Format("【{0}】 当前价{1}，当前MA{2}", StockCode, currentPrice.ToString("f2"), currentMA.ToString("f2")));
            }

            return tempsg;
        }

        /// <summary>
        /// 获取当前价，并检测指标信号
        /// </summary>
        /// <returns>
        /// 应发信号
        /// </returns>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-16 4:43 PM</datetime>
        /// </remarks>
        public Enums.Signal DetectSignal()
        {
            double currentPrice = 0.0;
            try
            {
                currentPrice = BusinessFactory.GetCurrentPriceProvider().GetCurrentPrice(StockCode);

                if (Configuration.AppSettings.GetValue<bool>("IsDebug"))
                {
                    Log.Info(string.Format("【{0}】当前价格：{1}", StockCode, currentPrice.ToString(".00")));
                }
            }
            catch (Exception e)
            {
                Log.Exception("行情价格获取失败，请检查系统配置", e);
            }

            return DetectSignal(currentPrice);
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// 计算历史MA值.
        /// </summary>
        /// <param name="index">MA分钟数.</param>
        /// <param name="historyClosePrice">历史价格列表.</param>
        /// <returns></returns>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:04 PM</datetime>
        /// </remarks>
        private double GetSMA(int index, List<double> historyClosePrice)
        {
            //历史数据不够
            if (index > historyClosePrice.Count)
                return 0;

            double sum = 0;
            for (int i = 0; i < index; i++)
            {
                sum += historyClosePrice[historyClosePrice.Count - 1 - i];
            }

            return sum / index;
        }

        /// <summary>
        /// 计算此次MA.
        /// </summary>
        /// <param name="index">MA分钟数.</param>
        /// <param name="historyClosePrice">历史价格列表.</param>
        /// <param name="currentPrice">当前收盘价.</param>
        /// <returns></returns>
        /// <remarks>
        /// 代码编写：<author>LANCEZHANG-PC\zhangxl</author><br/>
        /// 编写时间：<datetime>2012-10-17 4:12 PM</datetime>
        /// </remarks>
        private double GetSMA(int index, List<double> historyClosePrice, double currentPrice)
        {
            List<double> newPrices = new List<double>(historyClosePrice);
            newPrices.Add(currentPrice);

            return GetSMA(index, newPrices);
        }

        #endregion
    }
}
